package wizard;

import java.awt.BorderLayout;

import java.awt.CardLayout;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.Insets;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.util.HashMap;
import java.util.MissingResourceException;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.border.EmptyBorder;


/**
 * 
 * @author michau
 * Wizard class (very much) based on some Oracle tutorial. 'Next'/'Finish','Back','Cancel' buttons, not linear order of
 * panels etc. etc.
 *
 */

public class Wizard extends WindowAdapter implements PropertyChangeListener{

	// values that say how the dialog has been closed
	public static final int FINISH_RETURN_CODE = 0;
	public static final int CANCEL_RETURN_CODE = 1;
	public static final int ERROR_RETURN_CODE = 2;
	
	// String commands for buttons
	public static final String NEXT_BUTTON_ACTION_CMD = "nextButtonActionCmd";
	public static final String BACK_BUTTON_ACTION_CMD = "backButtonActionCmd";
	public static final String CANCEL_BUTTON_ACTION_CMD = "cancelButtonActionCmd";
	
	// text used for the buttons, loaded from property resource file
    static String BACK_TEXT;
    static String NEXT_TEXT;
    static String FINISH_TEXT;
    static String CANCEL_TEXT;
    
    // Icons for buttons from property resource file
    static Icon BACK_ICON;
    static Icon NEXT_ICON;
    static Icon FINISH_ICON;
    static Icon CANCEL_ICON;
    
    // models, controlers, dialogs etc
    private WizardModel wizardModel;
    private WizardController wizardController;
    private JDialog wizardDialog;
    
    private JPanel cardPanel;
    private CardLayout cardLayout;            
    private JButton backButton;
    private JButton nextButton;
    private JButton cancelButton;
    
    private int returnCode;
    
    // hashtable used by panels to store that may be needed in some other panel
    private HashMap<String, Object> storage;
    
    /**
     * Default constructor. This method creates a new WizardModel object and passes it
     * into the overloaded constructor.
     */    
    public Wizard() {
        this((Frame)null);
    }
    
    /**
     * 
     * @param parent Dialog Owner of the wizard object
     */
    public Wizard(Dialog parent){
    	wizardModel = new WizardModel();
    	wizardDialog = new JDialog(parent);
    	storage = new HashMap<String, Object>();
    	initComponents();
    }
    
    /**
     * 
     * @param parent Frame Owner of the wizard object
     */
    public Wizard(Frame parent){
    	wizardModel = new WizardModel();
    	wizardDialog = new JDialog(parent);
    	storage = new HashMap<String, Object>();
    	initComponents();
    }
    
    /**
     * @return The JDialog (parent) of this wizard
     */
    public JDialog getDialog(){
    	return wizardDialog;
    }
    
    /**
     * Shows or hides the dialog.
     * @param b boolean value indicating either to show or hide the dialog.
     * @return integer return code
     */
    public int showModalDialog(boolean b){
    	wizardDialog.setModal(true);
    	wizardDialog.setVisible(b);
    	
    	return returnCode;
    }
    
    // some methods to set or get JDialog Owner properties/elements
    public void setTitle(String title){
    	wizardDialog.setTitle(title);
    }
    public void setModal(boolean modal){
    	wizardDialog.setModal(modal);
    }
    public String getTitle(){
    	return wizardDialog.getTitle();
    }
    public boolean isModal(){
    	return wizardDialog.isModal();
    }
    
    /**
     * 
     * @return WizardModel model used by this wizard
     */
    public WizardModel getModel(){
    	return wizardModel;
    }
    
    /**
     * Add a Component as a panel for the wizard dialog by registering its
     * WizardPanelDescriptor object. Each panel is identified by a unique Object-based
     * identifier (often a String), which can be used by the setCurrentPanel()
     * method to display the panel at runtime.
     * @param id An Object-based identifier used to identify the WizardPanelDescriptor object.
     * @param panel The WizardPanelDescriptor object which contains helpful information about the panel.
     */    
    public void registerWizardPanel(Object id, WizardPanelDescriptor panel) {
        
        //  Add the incoming panel to our JPanel display that is managed by
        //  the CardLayout layout manager.
        cardPanel.add(panel.getPanelComponent(), id);
        
        //  Set a callback to the current wizard.
        panel.setWizard(this);
        
        //  Place a reference to it in the model. 
        wizardModel.registerPanel(id, panel);
        
    }     
    
    /**
     * Displays the panel with the id id (String usually) - the same used to register the panel
     * @param id The Object-based identifier of the panel to be displayed.
     */
    public void setCurrentPanel(Object id){
    	if (id == null)
    		close(ERROR_RETURN_CODE);
    	
    	// get the panel that is being displayed right now
    	WizardPanelDescriptor oldPanelDescriptor = wizardModel.getCurrentPanelDescriptor();
    	// if there is any
    	if (oldPanelDescriptor != null){
    		oldPanelDescriptor.aboutToHidePanel();
    	}
    	
    	// set the new panel
    	try{
    		wizardModel.setCurrentPanel(id);
    	}
    	catch (WizardPanelNotFoundException e){
    		System.out.println(e.getMessage());
    		close(ERROR_RETURN_CODE);
    	}
    	wizardModel.getCurrentPanelDescriptor().aboutToDisplayPanel();
    	
    	// show the panel
    	cardLayout.show(cardPanel, id.toString());
    	wizardModel.getCurrentPanelDescriptor().displayingPanel();
    	
    }
    

    /**
     * 
     * @param identifier String that identifies stored data
     * @param data Object Stored data
     */
    public void storePanelData(String identifier, Object data){
    	storage.put(identifier, data);
    }
    
    /**
     * 
     * @param identifier String that identifies stored data
     * @return Object Stored data
     */
    public Object getPanelData(String identifier){
    	return storage.get(identifier);
    }
    
    /**
     *
     * {@link PropertyChangeListener}
     * listens to property changes from the model to update dialog if necessary
     */
    public void propertyChange(PropertyChangeEvent e){

        if (e.getPropertyName().equals(WizardModel.CURRENT_PANEL_DESCRIPTOR_PROPERTY)) {
            wizardController.resetButtonsToPanelRules(); 
        } else if (e.getPropertyName().equals(WizardModel.NEXT_FINISH_BUTTON_TEXT_PROPERTY)) {            
            nextButton.setText(e.getNewValue().toString());
        } else if (e.getPropertyName().equals(WizardModel.BACK_BUTTON_TEXT_PROPERTY)) {            
            backButton.setText(e.getNewValue().toString());
        } else if (e.getPropertyName().equals(WizardModel.CANCEL_BUTTON_TEXT_PROPERTY)) {            
            cancelButton.setText(e.getNewValue().toString());
        } else if (e.getPropertyName().equals(WizardModel.NEXT_FINISH_BUTTON_ENABLED_PROPERTY)) {            
            nextButton.setEnabled(((Boolean)e.getNewValue()).booleanValue());
        } else if (e.getPropertyName().equals(WizardModel.BACK_BUTTON_ENABLED_PROPERTY)) {            
            backButton.setEnabled(((Boolean)e.getNewValue()).booleanValue());
        } else if (e.getPropertyName().equals(WizardModel.CANCEL_BUTTON_ENABLED_PROPERTY)) {            
            cancelButton.setEnabled(((Boolean)e.getNewValue()).booleanValue());
        } else if (e.getPropertyName().equals(WizardModel.NEXT_FINISH_BUTTON_ICON_PROPERTY)) {            
            nextButton.setIcon((Icon)e.getNewValue());
        } else if (e.getPropertyName().equals(WizardModel.BACK_BUTTON_ICON_PROPERTY)) {            
            backButton.setIcon((Icon)e.getNewValue());
        } else if (e.getPropertyName().equals(WizardModel.CANCEL_BUTTON_ICON_PROPERTY)) {            
            cancelButton.setIcon((Icon)e.getNewValue());
        }
    }
    
    /**
     * 
     * @return returnCode information on how the dialog was closed (the last set value)
     */
    public int getReturnCode(){
    	return returnCode;
    }
    
    /**
     * Mirrors the WizardModel method of the same name.
     * @return A boolean indicating if the button is enabled.
     */  
    public boolean getBackButtonEnabled() {
        return wizardModel.getBackButtonEnabled().booleanValue();
    }

   /**
     * Mirrors the WizardModel method of the same name.
     * @param boolean newValue The new enabled status of the button.
     */ 
    public void setBackButtonEnabled(boolean newValue) {
        wizardModel.setBackButtonEnabled(newValue);
    }

   /**
     * Mirrors the WizardModel method of the same name.
     * @return A boolean indicating if the button is enabled.
     */ 
    public boolean getNextFinishButtonEnabled() {
        return wizardModel.getNextFinishButtonEnabled().booleanValue();
    }

   /**
     * Mirrors the WizardModel method of the same name.
     * @param boolean newValue The new enabled status of the button.
     */ 
    public void setNextFinishButtonEnabled(boolean newValue) {
        wizardModel.setNextFinishButtonEnabled(newValue);
    }
 
   /**
     * Mirrors the WizardModel method of the same name.
     * @return A boolean indicating if the button is enabled.
     */ 
    public boolean getCancelButtonEnabled() {
        return wizardModel.getCancelButtonEnabled().booleanValue();
    }

    /**
     * Mirrors the WizardModel method of the same name.
     * @param boolean newValue The new enabled status of the button.
     */ 
    public void setCancelButtonEnabled(boolean newValue) {
        wizardModel.setCancelButtonEnabled(newValue);
    }

    /**
     * Closes the dialog and sets the return code.
     * @param code the return code
     */
    public void close(int code){
    	returnCode = code;
    	wizardDialog.dispose();
    	System.exit(ERROR_RETURN_CODE);
    }

    /**
     * Method to call from the customInitComponents method just in case you're too lazy to do GUI by hand
     * and want to copy paste it from netbeans. Remember to call the BUTTONS APROPRIATE NAMES THOUGH!
     * It adds listeners, sets text etc.
     */
    private void postInitComponents(){
        wizardModel.addPropertyChangeListener(this);       
        wizardController = new WizardController(this);
        
        wizardDialog.addWindowListener(this);
        
        backButton.setActionCommand(BACK_BUTTON_ACTION_CMD);
        nextButton.setActionCommand(NEXT_BUTTON_ACTION_CMD);
        cancelButton.setActionCommand(CANCEL_BUTTON_ACTION_CMD);

        backButton.addActionListener(wizardController);
        nextButton.addActionListener(wizardController);
        cancelButton.addActionListener(wizardController);
    }
    
    /**
     * Call this method instead of initComponents after copy pasting netbeans magic inside
     */
    private void customInitComponents(){
    	
    }
    
    private void initComponents() {

        wizardModel.addPropertyChangeListener(this);       
        wizardController = new WizardController(this);       

        wizardDialog.getContentPane().setLayout(new BorderLayout());
        wizardDialog.addWindowListener(this);
                
        //  Create the outer wizard panel, which is responsible for three buttons:
        //  Next, Back, and Cancel. It is also responsible a JPanel above them that
        //  uses a CardLayout layout manager to display multiple panels in the 
        //  same spot.
        
        JPanel buttonPanel = new JPanel();
        JSeparator separator = new JSeparator();
        Box buttonBox = new Box(BoxLayout.X_AXIS);

        cardPanel = new JPanel();
        cardPanel.setBorder(new EmptyBorder(new Insets(5, 10, 5, 10)));       

        cardLayout = new CardLayout(); 
        cardPanel.setLayout(cardLayout);
        
        //backButton = new JButton(new ImageIcon("com/nexes/wizard/backIcon.gif"));
        backButton = new JButton();
        nextButton = new JButton();
        cancelButton = new JButton();
        
        backButton.setActionCommand(BACK_BUTTON_ACTION_CMD);
        nextButton.setActionCommand(NEXT_BUTTON_ACTION_CMD);
        cancelButton.setActionCommand(CANCEL_BUTTON_ACTION_CMD);

        backButton.addActionListener(wizardController);
        nextButton.addActionListener(wizardController);
        cancelButton.addActionListener(wizardController);
        
        //  Create the buttons with a separator above them, then place them
        //  on the east side of the panel with a small amount of space between
        //  the back and the next button, and a larger amount of space between
        //  the next button and the cancel button.
        
        buttonPanel.setLayout(new BorderLayout());
        buttonPanel.add(separator, BorderLayout.NORTH);

        buttonBox.setBorder(new EmptyBorder(new Insets(5, 10, 5, 10)));       
        buttonBox.add(backButton);
        buttonBox.add(Box.createHorizontalStrut(10));
        buttonBox.add(nextButton);
        buttonBox.add(Box.createHorizontalStrut(30));
        buttonBox.add(cancelButton);
        
        buttonPanel.add(buttonBox, java.awt.BorderLayout.EAST);
        
        wizardDialog.getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH);
        wizardDialog.getContentPane().add(cardPanel, java.awt.BorderLayout.CENTER);

    }
    
    private static Object getImage(String name) {

        URL url = null;

        try {
            Class c = Class.forName("wizard.Wizard");
            url = c.getResource(name);
        } catch (ClassNotFoundException cnfe) {
            System.err.println("Unable to find Wizard class");
        }
        return url;

    }


    /**
     * If the user presses the close box on the dialog's window, treat it
     * as a cancel.
     * @param WindowEvent The event passed in from AWT.
     */ 
    
    public void windowClosing(WindowEvent e) {
        returnCode = CANCEL_RETURN_CODE;
    }
    
    static {

        try {         	
        	WizardResources resources = new WizardResources();
            
            BACK_TEXT = (String)(resources.getObject("backButtonText"));
            NEXT_TEXT = (String)(resources.getObject("nextButtonText"));
            CANCEL_TEXT = (String)(resources.getObject("cancelButtonText"));
            FINISH_TEXT = (String)(resources.getObject("finishButtonText"));
            
            BACK_ICON = new ImageIcon((URL)getImage((String)(resources.getObject("backButtonIcon"))));
            NEXT_ICON = new ImageIcon((URL)getImage((String)(resources.getObject("nextButtonIcon"))));
            CANCEL_ICON = new ImageIcon((URL)getImage((String)(resources.getObject("cancelButtonIcon"))));
            FINISH_ICON = new ImageIcon((URL)getImage((String)(resources.getObject("finishButtonIcon"))));
        
        } catch (MissingResourceException mre) {
        	mre.printStackTrace();
            System.out.println(mre);
            System.exit(1);
        }
    }

}
